#ifndef __POPOLAZIONE_H__
#define __POPOLAZIONE_H__

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <iostream>
#include "common.hpp"
#include "Individuo.hpp"
#include <fcntl.h>
#include <unistd.h>

//! Dimensione massima del torneo. In caso la dimensione fosse maggiore, viene automaticamente limitata.
#define MAX_TS  50

/** \class PopolazioneException
 *  \brief Eccezioni lanciate dalla classe Popolazione
 */
class PopolazioneException : public EHW_Exception
{
    public:
    //!Tipologie di errori
    enum error_type
    {
        ERR_TYPE_OOB,   //!< Tentativo di accesso ad una posizione fuori dai confini della popolazione
        ERR_TYPE_PSIZE, //!< Dimensione errata della popolazione durante la lettura da file
        ERR_TYPE_FILEIO //!< Errore il lettura/scrittura da file
    };

    //!Tipo di errore
    error_type et;

    PopolazioneException(char *mess,error_type err) : EHW_Exception(mess) {et=err;}
};

/** \class Popolazione
 *  \brief Memorizza una serie di individui ed implementa le operazioni base
 */
class Popolazione {

private:
    //!Array di individui che compongono la popolazione
    Individuo **P;

    //!Dimensione della popolazione
    unsigned int pop_size;

    //!Puntatore al miglior individuo della popolazione
    Individuo *Best;

	int POP_ROWS;
	int POP_COLS;


public:

	int pop_rows() {return(POP_ROWS);}
	int pop_cols() {return(POP_COLS);}


    //! Specifica la politica di resize in caso di lettura da file
    enum pop_load_respolicy {
        LOAD_RES_POLICY_RESIZE,     //!< Effettua il resize in caso la dimensione della popolazione sia diversa da quella letta da file
        LOAD_RES_POLICY_NORESIZE    //!< Non effettua resize e lancia un'eccezione
    };

    //! Modalita' di calcolo della distanza di hamming su tutta la popolazione
    enum hamming_type {
        HAMMING_TYPE_ALLMED,    //!< Distanza calcolata come media di tutte le distanze di ogni individuo da ciascun altro
        HAMMING_TYPE_BESTMED    //!< Distanza media di tutti gli individui dall'individuo migliore
    };

    //! Specifica quale tipo di crossover utilizzare
    enum crossover_type {
        CROSS_TYPE_SP       //!< Crossover Single Point
    };

    //! Specifica le opzioni della fase di crossover
    enum crossover_options {
        CROSS_OPT_P_ELIT = 1    //!< Preserve Elitism, non effettua crossover del miglior individuo che viene direttamente copiato
    };

    //! Specifica le opzioni della fase di mutazione
    enum mutation_options {
        MUT_OPT_P_ELIT = 1      //!< Preserve Elitism, non effettua mutazione sul miglior individuo
    };

	int resize(int new_size) throw(PopolazioneException);

    /** Alloca una popolazione di dimensione specificata
     *
     *  \param[in]  p_size  Dimensione della popolazione
	 *	\param[in]  rows	Numero di righe del genotipo degli individui
	 *	\param[in]	cols	Numero di colonne del genitipo degli individui
     */
    Popolazione(int p_size, int rows=-1 ,int cols=-1);

    /** Inizializza la popolazione leggendola direttamente da file
     *
     *  \param[in]  path    Path del file dal quale leggere la popolazione
     */
    Popolazione(char *path) throw(PopolazioneException);

    ~Popolazione();

    //!Inizializza tutti gli individui della popolazione con un genotipo casuale
    void    randomInit();
	
	void load_list(int n, Individuo * individuals[]);

    /** Legge la popolazione da file
     *
     *  \param[in]  path    Path del file dal quale leggere i dati della popolazione
     *  \param[in]  rp      Politica di ridimensionamento
     *
     *  \return     Numero di individui letti da file
     */
    int     load_from_file(char *path, enum pop_load_respolicy rp = Popolazione::LOAD_RES_POLICY_NORESIZE, unsigned int read_mod = 0) throw(PopolazioneException);

	/** Legge la popolazione da un fd gia' aperto
	 */
	int     load_from_file(int fp, enum pop_load_respolicy rp = Popolazione::LOAD_RES_POLICY_NORESIZE, unsigned int read_mod = 0) throw(PopolazioneException);

    /** Salva la popolazione corrente su file
     *
     *  \param[in]  path    Path del file sul quale scrivere i dati della popolazione
     */
    int     save_to_file(char *path, unsigned int write_mod = 0) throw(PopolazioneException);

	int     save_to_file(int fp,int save_from=0,int save_to=0, unsigned int write_mod = 0) throw(PopolazioneException);

    /** Calcola la distanza di hamming (secondo la modalita' specificata)
     *
     *  \param[in]  ht  Modalita' di calcolo della distanza
     *  \return         Distanza di hamming
     */
    float   hamming_distance(hamming_type ht=Popolazione::HAMMING_TYPE_ALLMED);

    /** Selezione con torneo ed elitismo.
     *  La popolazione N conterra' gli individui selezionati dalla popolazione sulla quale si chiama il metodo.
     *
     *  \param[out] N       La nuova popolazione ottenuta mediante selezione
     *  \param[in]  t_size  Dimensione del torneo
     *  \param[in]  sp      Probabilita' di vittoria del migliore individuo del torneo
     */
    void    selection_tournament_elitism(Popolazione &N,int t_size,float sp);

    /** Effettua il crossover per gli individui della popolazione.
     *  Per ogni coppia di individui \c adiacenti nella popolazione di partenza si effettua, con probabilita' \c cp, un crossover
     *  (altrimenti vengono direttamente copiati).
     *
     *  \param[out] N       Nuova popolazione
     *  \param[in]  cp      Probabilita' di crossover
     *  \param[in]  options Opzioni, vedi Popolazione::crossover_options
     *  \param[in]  ct      Specifica il tipo di crossover
     *  \return     Numero di crossover effettuati
     */
    int     crossover(Popolazione &N,float cp,HERAuint16 options = 0,crossover_type ct = Popolazione::CROSS_TYPE_SP);

    /** Mutazione attiva-adattativa su tutta la popolazione
     *
     *  \param[in]  min_rate    Rate minimo di mutazione
     *  \param[in]  max_rate    Rate massimo di mutazione
     *  \param[in]  max_score   Fitness massimo per la popolazione
     *  \param[in]  pm          Probabilita' di mutazione
     *  \param[in]  options     Opzioni di mutazione, vedi Popolazione::mutation_options
     *  \return                 Numero di bit mutati per tutta la popolazione
     *  \sa Individuo::mutazione_act_adapt
     */
    int     mutazione_act_adapt(int min_rate,int max_rate,int max_score,float pm,HERAuint16 options = 0);

    //! (Debug) Stampa il fitness di tutti gli individui
    void  print_all();

    //!Ritorna il miglior individuo della popolazione
    const Individuo &getBestIndividuo();

    //! Ritorna la dimensione della popolazione
    unsigned int  get_size() {return pop_size;}

    //! Calcola il valore di fitness medio e la varianza
    float mean_fitness(float *var);

    //! Copia due popolazioni
    void operator= (const Popolazione& pop);

	//!Alloca e ritorna una popolazione copia di quella attuale
	Popolazione *copy();


    /** Restituisce l'individuo in posizione specificata
     *
     *  \param[in]  index   Posizione dell'individuo
     */
    Individuo& operator[] (unsigned int index) throw(PopolazioneException);
	
	Individuo * get(int i);

    void sort_worse_individual(int n);
    void sort_best_individual(int n);
};

#endif
